package model;
import java.util.Iterator;
import java.util.List;
import java.util.Random;

/**
 * A class representing shared characteristics of animals.
 * 
 * @author Arjan Frans, Alwin Rombout, Jelte Verbree, Brendan Kanters
 * @version 1.0
 */
public abstract class Animal implements Actor
{
    // Whether the animal is alive or not.
    private boolean alive;
    // The animal's field.
    private Field field;
    // The animal's position in the field.
    private Location location;
    protected int age;
    protected int foodLevel;
    protected static final Random rand = Randomizer.getRandom();
    //Wether or not an animal is sick
    protected boolean isZiek;
    //How long an animal has been sick
    protected int ziekteDuur;
    //The duration in steps it takes for a sick animal to die
    protected static int ZIEKTE_INCUBATIE;
    //The chance to catch the virus
    private static Double INFECTION_CHANCE;
    //The chance to be susceptible to the virus
    protected static double ZIEKTE_GEN_CHANCE = 0.9; 
   
  
    /**
     * Create a new animal at location in field.
     * 
     * @param field The field currently occupied.
     * @param location The location within the field.
     */
    public Animal(Field field, Location location)
    {
        alive = true;
        this.field = field;
        setLocation(location);
        age=0;
        isZiek = false;
        ziekteDuur = 0;
    }
    
    /**
     * Check whether or not this fox is to give birth at this step.
     * New births will be made into free adjacent locations.
     * @param newAnimals A list to add newly born foxes to.
     */
    protected void giveBirth(List<Actor> newAnimals)
    {
        // New foxes are born into adjacent locations.
        // Get a list of adjacent free locations.
        Field field = getField();
        List<Location> free = field.getFreeAdjacentLocations(getLocation());
		    int births = breed();
		    for(int b = 0; b < births && free.size() > 0; b++) {
		        Location loc = free.remove(0);
		        if(field.getRoadLocation(loc.getRow(), loc.getCol()) == false) {
			        Animal young = null;
			        if(this instanceof Wolf) {
			        	young = new Wolf(false, field, loc);
			        }
			        if(this instanceof Fox) {
			        	young = new Fox(false, field, loc);         
			        }
			        if(this instanceof Rabbit) {
			        	Rabbit wabbit = (Rabbit) this;
			        	young = new Rabbit(false, field, loc, wabbit.getZiekteGen());    
			        }           	            
					newAnimals.add(young);
		        }
		    }
    }
    /**
     * Make this animal more hungry. This could result in the animal's death.
     */
    protected void incrementHunger()
    {
        foodLevel--;
        if(foodLevel <= 0) {
            setDead();
        }
    }
    
    /**
     * Make this animal act - that is: make it do
     * whatever it wants/needs to do.
     * @param newAnimals A list to add newly born animals to.
     */
    abstract public void act(List<Actor> newAnimals);

    /**
     * Check whether the animal is alive or not.
     * @return true if the animal is still alive.
     */
    public boolean isAlive()
    {
        return alive;
    }

    /**
     * Indicate that the animal is no longer alive.
     * It is removed from the field.
     */
    public void setDead()
    {
        alive = false;
        if(location != null) {
            field.clear(location);
            location = null;
            field = null;
        }
    }

    /**
     * Return the animal's location.
     * @return The animal's location.
     */
    public Location getLocation()
    {
        return location;
    }
    
    /**
     * Return the animal's field.
     * @return The animal's field.
     */
    public Field getField()
    {
        return field;
    }
    
    
    /**
     * Place the animal at the new location in the given field.
     * @param newLocation The animal's new location.
     */
    public void setLocation(Location newLocation)
    {
        if(location != null) {
            field.clear(location);
        }
        location = newLocation;
        field.place(this, newLocation);
    }
    
    /**
     * Method to set the age
     * @param age
     */
    public void setAge(int age)
    {
        this.age = age;
    }
    
    /**
     * Method to get the age
     * @return age
     */
    public int getAge()
    {
        return age;
    } 
    
    /**
     * abstract method getMaxAge()
     */
    abstract protected int getMaxAge();
    
    /**
     * Increase the age.
     * This could result in the animal's death.
     */
    public void incrementAge()
    {
        age++;
        if(age > getMaxAge()) {
            setDead();
        }
    }
    
    /**
     * Return the breeding age of this animal
     * @return Breeding age of animal
     */
    abstract protected int getBreedingAge();
    
    /**
     * An animal can breed when it has reached his
     * breeding age
     * @return true if the animal can breed.
     */
    public boolean canBreed()
    {
    	return age >= getBreedingAge();
    }
    
    /**
     * @return Return the breeding probability
     */
    abstract protected double getBreedingProbability();
    
    /**
     * @return Return max litter size
     */
    abstract protected int getMaxLitterSize();
    
    /**
     * Generate a number representing the number of births,
     * if it can breed.
     * @return The number of births (may be zero).
     */
    protected int breed()
    {
        int births = 0;
        //if max litter size is zero no action needs to be taken.
        if(getMaxLitterSize() != 0) {
	        if(canBreed() && rand.nextDouble() <= getBreedingProbability()) {
	            births = rand.nextInt(getMaxLitterSize()) + 1;
	        }
        }
	    return births;
    }
    
    /**
     * Method to see if the animal has the isZiek
     * @return isZiek
     */
    public boolean getIsZiek()
    {
    	return isZiek;
    }
    
    /**
     * Method to set the isZiek
     * @param ziekte
     */
    public void setIsZiek(boolean ziekte)
    {
    	isZiek = ziekte;
    }
    
    /**
     * Method to infect an animal through airborne (proximity) infection
     */
    protected void infect()
    {
    	Rabbit wabbit;
    	if(this instanceof Rabbit){
    		wabbit = (Rabbit) this;
    	}
    	else {
    		return;
    	}
    	if(wabbit.getZiekteGen()){
    		Field field = getField();
    		List<Location> adjacent = field.adjacentLocations(getLocation());
    		Iterator<Location> it = adjacent.iterator();
    		while(it.hasNext()) {
    			Location where = it.next();
    			Object animal = field.getObjectAt(where);
    			if(animal instanceof Animal) {
    				Animal carrier = (Animal) animal;
    				if(carrier.isAlive() && carrier.getIsZiek()) { 
    					if(rand.nextDouble() <= INFECTION_CHANCE){
    						setIsZiek(true);
    					}
    				}
    			}
    		}
    	}
    }
    
    /**
     * Method used when an animal has the virus
     */
    protected void incrementZiekte()
    {
    	if(getIsZiek() && isAlive()){
    		ziekteDuur++;
    		if(ziekteDuur >= ZIEKTE_INCUBATIE){
    			setDead();
    			return;
    		}
    	}
    }
    
    /**
     * Method to set the infection chance
     * @param inf
     */
    public static void setInfection(Double inf)
    {
    	INFECTION_CHANCE = inf;
    }
    
    /**
     * Method to set the incubation time
     * @param inc
     */
    public static void setIncubation(int inc)
    {
    	ZIEKTE_INCUBATIE = inc;
    }
  
}

